<?php

namespace Payment\Common\Fu;

use Payment\Common\BaseData;
use Payment\Common\BaseStrategy;
use Payment\Common\PayException;
use Payment\Common\FuConfig;
use Payment\Utils\Curl;
use Payment\Utils\DataParser;


abstract class FuBaseStrategy implements BaseStrategy
{


    protected $config;

    /**
     * 支付数据
     * @var BaseData $reqData
     */
    protected $reqData;

    /**
     * FuBaseStrategy constructor.
     * @param array $config
     * @throws PayException
     */
    public function __construct(array $config)
    {
        /* 设置内部字符编码为 UTF-8 */
//        mb_internal_encoding("UTF-8");

        try {
            $this->config = new FuConfig($config);
        } catch (PayException $e) {
            throw $e;
        }
    }

    /**
     * 发送请求
     * @param array $body
     * @return mixed
     * @throws PayException
     */
    protected function sendReq($body)
    {
        $url = $this->getReqUrl();
        if (is_null($url)) {
            throw new PayException('目前不支持该接口。请联系开发者添加');
        }
        $body = DataParser::toXml($body);
        if (empty($body)) throw new PayException('支付数据异常');
        $response = $this->curlPost('req=' . urlencode(urlencode(mb_convert_encoding('<?xml version="1.0" encoding="GBK" standalone="yes"?>' . $body, 'gbk', 'utf-8'))), $url); //请求的数据的编码需从utf-8转为gbk，否则客户端显示会乱码
        if ($response['error']) throw new PayException('网络发生错误，请稍后再试curl返回码：' . $response['message']);
//        $retData = DataParser::toArray(mb_convert_encoding(urldecode($response['body']), 'utf-8', 'gbk')); //将返回数据的编码从gbk转为utf-8后包含中文就解析失败，没有深究
        $retData = DataParser::toArray(urldecode($response['body']));
        if (empty($retData['result_code'])) throw new PayException('支付平台返回错误提示5:系统繁忙 hint: [code does not exist]');
        if ($retData['result_code'] != '000000') throw new PayException('支付平台返回错误提示5:' . $retData['result_msg'] ?? $retData['result_code']);

        //TODO: 解析出来的数据如果为空会变成数组，在此处将空数组转为空字符串（可能是由于xml空节点在转为对象时已经变成了空对象，在转成数组也就成为了空数组）
        foreach ($retData as $key => &$value) $value = is_array($value) && empty($value) ? '' : $value;
        unset($value);

        return $retData;
    }

    /**
     * 请求
     * @param $body
     * @param $url
     * @return array
     */
    protected function curlPost($body, $url)
    {
        $curl = new Curl();
        $header = [
            'content-type: application/x-www-form-urlencoded'
        ];
        return $curl->set([
            'CURLOPT_HEADER' => 0,
            'CURLOPT_HTTPHEADER' => $header
        ])->post($body)->submit($url, false);
    }

    /**
     * 获取请求地址
     * @param null $url
     * @return string
     */
    protected function getReqUrl($url = null)
    {
        return $this->config->base_url . ($url ?? FuConfig::UNIFIED_URL);
    }

    /**
     * @param array $data
     *
     * @throws PayException
     * @throws \Exception
     * @return array|string
     */
    public function handle(array $data)
    {
        $buildClass = $this->getBuildDataClass();
        $this->reqData = new $buildClass($this->config, $data);
        $this->reqData->setSign();
        $body = $this->reqData->getData();
        $ret = $this->sendReq($body);

        $flag = true;// $this->verifySign($ret);
        if (!$flag) {
            throw new PayException('返回数据被篡改。请检查网络是否安全！');
        }
        return $this->retData($ret);
    }

    /**
     * 处理返回值并返回给客户端
     * @param array $ret
     * @return mixed
     *
     */
    protected function retData(array $ret)
    {
        return $ret;
    }

    /**
     * 签名验证
     * @param array $retData
     * @return bool
     * @throws PayException
     */
    protected function verifySign(array $retData)
    {
        $retSign = $retData['sign'];

        if (empty($this->publicKey)) return false;
        $publicKey = is_file($this->publicKey) ? file_get_contents($this->publicKey) : $this->publicKey;
        if (empty($publicKey)) throw new PayException('Public key error');

        $pubkey = openssl_pkey_get_public($publicKey);
        if (empty($pubkey)) throw new PayException('Public key resource identifier false');

        $data = '';
        $result = openssl_verify($data, base64_decode($retSign), $pubkey, OPENSSL_ALGO_MD5);

        return 1 === $result;
    }

}
